1 /* 2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021 3 License: [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License]. 4 Authors: Marcelo S. N. Mancini 5 6 Copyright Marcelo S. N. Mancini 2018 - 2021. 7 Distributed under the CC BY-4.0 License. 8 (See accompanying file LICENSE.txt or copy at 9 https://creativecommons.org/licenses/by/4.0/ 10 */ 11 module tools.insert_resources_uwp; 12 import std.conv:to; 13 import std.process; 14 import std.string; 15 import std.algorithm:countUntil, filter; 16 import std.file; 17 import std.array; 18 import commons; 19 20 enum vcxItemTypes = 21 [ 22 ///Images 23 ".png" : "Image", 24 ".tiff": "Image", 25 ".jpeg": "Image", 26 ".jpg" : "Image", 27 ".bmp" : "Image", 28 ".webp": "Image", 29 ///Binary 30 ".ttf" : "Document", 31 ".dll" : "Document", 32 ///Text 33 ".txt" : "Text", 34 ".fnt" : "Text", 35 ".conf": "Text", 36 ".ini" : "Text", 37 ".json": "Text", 38 ".tsx" : "Text", 39 ".tmx" : "Text", 40 ".xml" : "Text", 41 ///Audio 42 ".wav" : "Media", 43 ".mp3" : "Media", 44 ".ogg" : "Media" 45 ]; 46 47 48 enum specialLabel = "<ItemGroup Label=\"ResourceCopy\">"; 49 enum wordToFind = "</ItemGroup>"; 50 51 52 /** 53 * 54 * Params: 55 * t = Terminal for printing 56 * fileName = The file name 57 * Returns: The type from the vcxItemTypes 58 */ 59 string getType(ref Terminal t, string fileName) 60 { 61 string type = fileName.extension; 62 string* uwpType = (type in vcxItemTypes); 63 if(uwpType == null) 64 { 65 t.writelnHighlighted("File named ", fileName, " is an unrecognized type, returning as None"); 66 return "None"; 67 } 68 return *uwpType; 69 } 70 71 72 bool stripProject(ref Terminal t, ref string res, string fileName) 73 { 74 long start = res.countUntil(specialLabel); 75 if(start == -1) 76 return true; 77 78 size_t itemGroupDepth = 0; 79 for(size_t i = start+specialLabel.length; i < res.length; i++) 80 { 81 size_t nextItemGroup = res[i..$].countUntil("<ItemGroup>"); 82 size_t nextItemGroupEnd = res[i..$].countUntil("</ItemGroup>"); 83 84 85 if(nextItemGroupEnd == -1 || nextItemGroup == -1) 86 throw new Exception("Could not find tokens on file "~fileName); 87 88 if(nextItemGroupEnd < nextItemGroup) 89 { 90 i+= nextItemGroupEnd + "</ItemGroup>".length; 91 if(itemGroupDepth == 0) 92 { 93 res = res[0..cast(size_t)start] ~ res[i..$]; 94 return true; 95 } 96 itemGroupDepth--; 97 } 98 else if(nextItemGroup < nextItemGroupEnd) 99 { 100 i+= nextItemGroup + "<ItemGroup>".length; 101 itemGroupDepth++; 102 } 103 } 104 return false; 105 } 106 107 string getResourceDescriptor(ref Terminal t, DirEntry e, string targetPath) 108 { 109 string n = "$(ProjectDir)\\UWPResources"~e.name[targetPath.length..$]; 110 return ("<"~getType(t, e.name) ~" Include=\""~ n ~ "\"/>"); 111 } 112 113 string getResourceFilterDescriptor(ref Terminal t, DirEntry e, string targetPath) 114 { 115 string type = getType(t, e.name); 116 string filterName = e.name[targetPath.length-"UWPResources\\".length..$]; 117 118 long ind = lastIndexOf(filterName, '\\'); 119 if(ind == -1) 120 { 121 t.writelnError("Unexpected Error: Resource is a directory."); 122 return ""; 123 } 124 filterName = filterName[0..cast(uint)ind]; 125 if(filterName[0] == '\\')filterName = filterName[1..$]; 126 127 string n = "$(ProjectDir)\\UWPResources"~e.name[targetPath.length..$]; 128 129 return ("<"~type ~" Include=\""~ n ~ "\">\n\t<Filter>"~filterName~"</Filter>\n</"~type~">"); 130 } 131 132 string getFilterName(DirEntry e, string targetPath) 133 { 134 string descriptor = e.name[targetPath.length-"UWPResources\\".length..$]; 135 long ind = lastIndexOf(descriptor, '\\'); 136 descriptor = descriptor[0..cast(uint)ind]; 137 if(descriptor[0] == '\\')descriptor = descriptor[1..$]; 138 return descriptor; 139 } 140 141 142 string generateFilterDefinition(string[] filters) 143 { 144 string retVal = ""; 145 foreach(f; filters) 146 retVal~="<Filter Include=\""~f~"\"/>\n"; 147 return retVal[0..$-1]; 148 } 149 150 bool hasFilter(string filterName, const string[] filters) 151 { 152 for(int i = 0; i < filters.length; i++) 153 if(filters[i] == filterName) 154 return true; 155 return false; 156 } 157 158 159 void generateFilters(string filterName, ref string[] filters) 160 { 161 string[] paths = pathSplitter(filterName).array; 162 for(int i = 0; i < paths.length; i++) 163 { 164 string toGenerate = ""; 165 for(int z = i; z >= 0; z--) 166 toGenerate = paths[z]~"\\"~toGenerate; 167 if(toGenerate[$-1] == '\\') 168 toGenerate = toGenerate[0..$-1]; 169 if(!hasFilter(toGenerate, filters)) 170 filters~= toGenerate; 171 } 172 } 173 174 175 bool stripUWPResources(ref Terminal t, ref string vcx, ref string vcxfilter) 176 { 177 if(!stripProject(t, vcx, ".vcxproj")) 178 { 179 t.writelnError(`Could not strip last resource. `); 180 return false; 181 } 182 if(!stripProject(t, vcxfilter,".vcxproj.filters")) 183 { 184 t.writelnError(`Could not strip last resource filters.`); 185 return false; 186 } 187 return true; 188 } 189 190 bool insertUWPResources(ref Terminal t, string uwpVcxProjPath, string assetsToImport) 191 { 192 import std.path; 193 import tools.copyresources; 194 string vcxPath = buildNormalizedPath(uwpVcxProjPath, baseName(uwpVcxProjPath)~".vcxproj"); 195 if(!exists(vcxPath)) 196 { 197 t.writelnError(vcxPath, " does not exists."); 198 return false; 199 } 200 if(!exists(assetsToImport)) 201 { 202 t.writelnError(assetsToImport, " does not exists. "); 203 return false; 204 } 205 string targetPath = buildNormalizedPath(uwpVcxProjPath, "UWPResources"); 206 copyResources(t, assetsToImport, targetPath, false); 207 208 string vcx = to!string(readText(vcxPath)); 209 string vcxfilter = to!string(readText(vcxPath~".filters")); 210 211 212 if(!stripUWPResources(t, vcx, vcxfilter)) 213 { 214 t.writelnError("Could not strip UWPResources '"~specialLabel~'\''); 215 return false; 216 } 217 218 long startIndex = cast(int)vcx.countUntil(wordToFind); 219 long startIndexFilter = cast(int)vcxfilter.countUntil(wordToFind); 220 if(startIndex == -1 || startIndexFilter == -1) 221 { 222 t.writelnError("File format is not yet supported."); 223 return false; 224 } 225 226 startIndex +=wordToFind.length; 227 startIndexFilter+=wordToFind.length; 228 229 230 string toAppend = specialLabel; 231 string toAppendFilter = ""; 232 233 string[] filters; 234 235 236 foreach(DirEntry e; dirEntries(targetPath, SpanMode.depth).filter!((DirEntry entry) => entry.isFile)) 237 { 238 string resource = getResourceDescriptor(t, e, targetPath); 239 string filter = getResourceFilterDescriptor(t, e, targetPath); 240 if(resource == "") 241 { 242 t.writelnError("Fatal Error. Stopping resource insertion process."); 243 return false; 244 } 245 string filterDef = getFilterName(e, targetPath); 246 247 if(!hasFilter(filterDef, filters)) 248 generateFilters(filterDef, filters); 249 250 toAppend~= resource~"\n"; 251 toAppendFilter ~= filter~"\n"; 252 } 253 254 toAppend~= "</ItemGroup>"; 255 toAppendFilter = specialLabel~generateFilterDefinition(filters)~"\n"~toAppendFilter~"</ItemGroup>"; 256 257 long plusIndex = vcx.countUntil('<'); 258 if(plusIndex != 0) plusIndex--; 259 260 long plusIndexFilter = vcxfilter.countUntil('<'); 261 if(plusIndexFilter != 0) plusIndexFilter--; 262 263 //Add files to the project 264 vcx = 265 vcx[plusIndex..cast(uint)startIndex+plusIndex]~ 266 toAppend~ 267 vcx[cast(uint)startIndex+plusIndex..$]; 268 269 //Add filters to the project 270 vcxfilter = 271 vcxfilter[plusIndexFilter..cast(uint)startIndexFilter+plusIndexFilter]~ 272 toAppendFilter~ 273 vcxfilter[cast(uint)startIndexFilter+plusIndexFilter..$]; 274 275 276 t.writelnSuccess("Writing .vcxproj and .vcxproj.filters"); 277 std.file.write(vcxPath, vcx); 278 std.file.write(vcxPath~".filters", vcxfilter); 279 280 return true; 281 }